/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						 sound.cpp

	$Header: /heroes4/sound.cpp $

	$NoKeywords: $

 ************************************************************************/
// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "sound.h"

#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>

#include <mmsystem.h>
#include <dsound.h>
#include <math.h>
#include <vector>
#include "bound_handler.h"
#include "data_lock.h"
#include "idle_processor.h"
#include "game_time.h"
#include "operating_system_interface.h"
#include "playing_sound.h"
#include "sound_handle_ptr.h"
#include "sound_stream.h"
#include "streambuf_operators.h"
#include "thread.h"

char const t_resource_traits<t_sound>::prefix[] = "sound";

char const t_resource_traits<t_sound>::wave_extension[] = "wav";

char const t_resource_traits<t_sound>::mp3_extension[] = "mp3";

namespace
{
	int k_sample_rate							= 44100;
	int k_bit_per_sample						= 16;
	int k_number_of_channels					= 2;	

	// ---------------------------------------------------------
	// handle synchronization events
	// ---------------------------------------------------------
	class t_sync_list
	{
	public:
		~t_sync_list();
		void add_event( HANDLE handle, t_handler handler );
		void clear();
		void on_idle();
	protected:
		std::vector<HANDLE>    m_handles;
		std::vector<t_handler> m_handlers;
	};

	inline void t_sync_list::add_event( HANDLE handle, t_handler handler )
	{
		m_handles.push_back( handle );
		m_handlers.push_back( handler );
	}

	// ---------------------------------------------------------
	// class to play sounds
	// ---------------------------------------------------------
	class t_sound_player : public t_playing_sound
	{
	public:
		t_sound_player( t_sound const& sound );
		virtual ~t_sound_player();

		virtual void fade_out( bool close_on_stop = true );
		virtual int  get_balance() const;
		virtual int  get_type() const;
		virtual int  get_volume() const;
		virtual bool is_fading_out() const;
		virtual void play( int volume, bool looping, bool fade_in );
		virtual void set_balance( int arg );  // -10000 (left) to 10000 (right)
		virtual void set_volume( int volume, bool interpolate_to_volume = false ); // 0 to -10000
		virtual void stop( bool and_close = true );
		void         update();

	protected:
		void check_stop();
		void close_buffer();
		void create_buffer();
		void create_events( int type = 0);
		void fill_first_half();
		void fill_second_half();
		void fill_buffer( int start, int stop );
		void on_stop();

		int							m_data_remaining;
		t_sync_list					m_sync_list;
		LPDIRECTSOUNDBUFFER			m_buffer;
		int							m_buffer_size;
		bool						m_final_pass;
		int							m_final_position;
		t_uint32					m_last_update_time;
		LPDIRECTSOUNDNOTIFY			m_notify_object;
		int							m_second_half_start;
		bool						m_streaming;
		int							m_tolerance;
		long						m_volume_now;
		long						m_volume_target;

		t_counted_ptr<class t_direct_sound_object> m_direct_sound_ptr;
	};

	typedef t_counted_ptr<t_sound_player> t_sound_player_ptr;

	// ---------------------------------------------------------
	// thread object to service sounds
	// ---------------------------------------------------------
	class t_sound_thread : public t_thread
	{
	public:
		virtual t_uint32 run();
	};

	// ---------------------------------------------------------
	// wrapper for direct sound top level routines
	// ---------------------------------------------------------
	class t_direct_sound_object : public t_direct_sound_wrapper_base
	{
	public:
		t_direct_sound_object();
		~t_direct_sound_object();

		void						add( t_sound_player* player );
		LPDIRECTSOUNDBUFFER			create_buffer( WAVEFORMATEX* format, int size, int type );
		bool						is_shut_down() const;
		t_data_lock&				get_data_lock();
		void						remove( t_sound_player* player );
		void						service_sounds();
		virtual LPDIRECTSOUND const	get_directsound_ptr();
		void						stop_sounds();

	protected:
		LPDIRECTSOUND					m_direct_sound;
		bool							m_is_shut_down;
		bool							m_own_directsound;
		LPDIRECTSOUNDBUFFER			    m_primary_buffer;
		std::vector<t_sound_player_ptr> m_sounds;
		t_data_lock                     m_data_lock;
		t_sound_thread                  m_thread;
	};
};

// --------------------------------------------------------------------
// base class t_direct_sound_wrapper_base implementation
// --------------------------------------------------------------------
t_direct_sound_wrapper_base::~t_direct_sound_wrapper_base()
{

}

LPDIRECTSOUND const	t_direct_sound_wrapper_base::get_directsound_ptr()
{
	return NULL;
}

// --------------------------------------------------------------------
// direct sound object
// --------------------------------------------------------------------

// --------------------------------------------------------------------
// direct sound object
// --------------------------------------------------------------------
t_direct_sound_object::t_direct_sound_object()
{
	m_own_directsound	= false;
	m_direct_sound		= NULL;
	m_primary_buffer	= NULL;
	m_is_shut_down		= false;

	//////////////////////////////////////////////////////////////////////////////////////
	//initalize DirectSound for our code
	//////////////////////////////////////////////////////////////////////////////////////

	HRESULT			result;
	DSBUFFERDESC	sound_buffer_description;

	m_direct_sound = NULL;
	result = DirectSoundCreate( 0, &m_direct_sound, 0);
	
	if (result != DS_OK || !m_direct_sound) 
	{
		m_direct_sound = NULL;
		return;
	}

	m_direct_sound->SetCooperativeLevel( t_operating_system_interface::get_game_windows_handle(), DSSCL_PRIORITY );

	memset( &sound_buffer_description, 0, sizeof( sound_buffer_description ));
	sound_buffer_description.dwSize = sizeof( sound_buffer_description );
	sound_buffer_description.dwFlags = DSBCAPS_PRIMARYBUFFER;
	m_direct_sound->CreateSoundBuffer( &sound_buffer_description, &m_primary_buffer, NULL);

    // Set primary buffer format
    WAVEFORMATEX format;

    memset(&format, 0, sizeof(format)); 

    format.wFormatTag = WAVE_FORMAT_PCM; 
    format.nChannels = 2; 
    format.nSamplesPerSec  = 44100; 
    format.wBitsPerSample  = 16; 
    format.nBlockAlign	   = format.wBitsPerSample / 8 * format.nChannels;
    format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
    m_primary_buffer->SetFormat(&format); 

	m_own_directsound = true;
}

t_direct_sound_object::~t_direct_sound_object()
{
	// shouldnt be any. should be stopped already via stop_all_sounds
	stop_sounds();					
	m_thread.stop();
	
	if (m_direct_sound && m_own_directsound) 
	{
		m_direct_sound->Release();
		m_direct_sound = NULL;
	}
}

bool t_direct_sound_object::is_shut_down() const
{
	return m_is_shut_down;
}

// --------------------------------------------------------------------
// direct sound object stop_all_sounds
// --------------------------------------------------------------------
void t_direct_sound_object::stop_sounds()
{
	int i;
	m_data_lock.lock();
	for (i = m_sounds.size() - 1; i >= 0; i--)
		m_sounds[i]->stop();
	m_sounds.clear();
	m_data_lock.unlock();
	m_is_shut_down = true;
}

// --------------------------------------------------------------------
// direct sound object
// --------------------------------------------------------------------
inline t_data_lock& t_direct_sound_object::get_data_lock() 
{
	return m_data_lock;
}

// --------------------------------------------------------------------
// direct sound object
// --------------------------------------------------------------------
static const int k_min_volume = -10000;	// Defined by DirectSound
static const int k_max_volume = 0;		// Defined by DirectSound

void t_direct_sound_object::add( t_sound_player* player )
{
	m_data_lock.lock();
	m_sounds.push_back( player );
	m_data_lock.unlock();

	m_thread.start();
}

// --------------------------------------------------------------------
// direct sound object
// --------------------------------------------------------------------
LPDIRECTSOUND const t_direct_sound_object::get_directsound_ptr()
{
	return m_direct_sound;
}

// --------------------------------------------------------------------
// direct sound object
// --------------------------------------------------------------------
void t_direct_sound_object::remove( t_sound_player* player )
{
	int           i;
	t_data_locker lock( m_data_lock );

	for (i = 0; i < m_sounds.size(); i++)
		if (m_sounds[i].get() == player)
		{
			m_sounds.erase( m_sounds.begin() + i );
			return;
		}
}

// --------------------------------------------------------------------
// direct sound object
// --------------------------------------------------------------------
void t_direct_sound_object::service_sounds()
{
	t_data_locker lock( m_data_lock );
	int			  i;

	for (i = m_sounds.size() - 1; i >= 0; i--)
		m_sounds[i]->update();
}

// --------------------------------------------------------------------
// direct sound object
// --------------------------------------------------------------------
LPDIRECTSOUNDBUFFER t_direct_sound_object::create_buffer( WAVEFORMATEX* format, int size , int type )
{
	DSBUFFERDESC        sound_buffer_description;
	LPDIRECTSOUNDBUFFER result;

	assert( m_direct_sound );

    memset(&sound_buffer_description, 0, sizeof(sound_buffer_description)); 
    sound_buffer_description.dwSize = sizeof(sound_buffer_description); 
  
	sound_buffer_description.dwFlags =  
		DSBCAPS_GETCURRENTPOSITION2		// Always a good idea
		| DSBCAPS_GLOBALFOCUS			// Allows background playing
		| DSBCAPS_CTRLVOLUME 
		| DSBCAPS_CTRLPAN 
		| DSBCAPS_LOCDEFER				// allow prioritization of sounds
		| DSBCAPS_CTRLPOSITIONNOTIFY	// Needed for notification
		| DSBCAPS_STATIC  ;
	
    // The size of the buffer is arbitrary, but should be at least
    // two seconds, to keep data writes well ahead of the play
    // position.

    sound_buffer_description.dwBufferBytes	= size;  
    sound_buffer_description.lpwfxFormat	= format;

    if ( m_direct_sound->CreateSoundBuffer( &sound_buffer_description, &result, NULL) < 0 )
		return 0;

	return result;
}

// --------------------------------------------------------------------
// gets the direct sound object
// --------------------------------------------------------------------
static t_direct_sound_object& get_direct_sound()
{
	static t_counted_ptr<t_direct_sound_object> directsound = new t_direct_sound_object;
	return *directsound;
}

// --------------------------------------------------------------------
// gets the direct sound wrapper which is refrence counted to ensure proper destruction order
// --------------------------------------------------------------------
t_direct_sound_wrapper_base& get_directsound_wrapper()
{
	return get_direct_sound();
}

// --------------------------------------------------------------------
// direct sound thread
// --------------------------------------------------------------------
t_uint32 t_sound_thread::run()
{
	while (!stop_requested())
	{
		get_direct_sound().service_sounds();
		Sleep( 250 );
	}
	return 0;
}

// ---------------------------------------------------------
// abstract sound
// ---------------------------------------------------------
t_sound_header::t_sound_header()
{
	m_bits_per_sample		= 0;
	m_channels				= 0;
	m_samples_per_second	= 0;
	m_size					= 0;
	m_priority              = 1;
}

bool t_sound_header::read( std::streambuf& stream )
{
	int version;

	version				 = get<t_uint16>( stream );
	if (version > k_version)
		return false;
	m_bits_per_sample	 = get<t_uint8>( stream );
	m_channels			 = get<t_uint8>( stream  );
	m_samples_per_second = get<t_uint32>( stream );
	m_size				 = get<t_uint32>( stream );
	m_priority           = get<t_uint16>( stream );
	return true;
}

// ---------------------------------------------------------
// handle synchronization events
// ---------------------------------------------------------
t_sync_list::~t_sync_list()
{
	clear();
}

// ---------------------------------------------------------
// handle synchronization events
// ---------------------------------------------------------
void t_sync_list::clear()
{
	int i;

	for (i = 0; i < m_handles.size(); i++)
		CloseHandle( m_handles[i] );
	m_handles.clear();
	m_handlers.clear();
}

// ---------------------------------------------------------
// handle synchronization events
// ---------------------------------------------------------
void t_sync_list::on_idle()
{
	if (m_handles.size() == 0)
		return;

    DWORD result;
	int   i;
	
	for (i = 0; i < m_handles.size(); i++)
	{
		result = WaitForSingleObject( m_handles[i], 0 );
		if (result == WAIT_OBJECT_0)
		{
			m_handlers[i]();
		}
	}
}

// ------------------------------------------------------
// abstract class representing a playing sound
// ------------------------------------------------------
t_playing_sound::t_playing_sound( t_sound const& sound ) : m_sound( sound )
{
	m_sound_data			= m_sound->get_stream();
	m_looping				= false;
	m_playing				= false;
	m_fading_in				= false;
	m_close_on_stop			= true;
	m_priority				= 0;
	m_play_position_state	= k_playing_uninitalized_buffer;
	m_type					= 0;
}


// ---------------------------------------------------------
// sound details
// ---------------------------------------------------------
t_sound_player::t_sound_player( t_sound const& sound ) : t_playing_sound( sound )
{
	// Hold a lock on the direct sound object until we die
	// This is to ensure we can continue to use the directsound object's
	// critical section object during our destruction.
	m_direct_sound_ptr = &get_direct_sound();
	assert( m_direct_sound_ptr.get() );

	// determine size of sound buffer
	m_streaming		= true;
	m_final_pass	= false;

	//inital buffer size is set to 2 secs worth
	m_buffer_size = 2*(m_sound->get_samples_per_second() * m_sound->get_bits_per_sample() 
		            * m_sound->get_channels() / 4);

	if (m_buffer_size >= m_sound->get_size())
	{
		m_streaming = false;
		m_buffer_size = m_sound->get_size();
	}

	m_second_half_start		= m_buffer_size / 2;
	m_tolerance				= (float)m_second_half_start*.01;
	m_data_remaining		= m_sound->get_size();
	m_notify_object			= 0;
	m_buffer				= 0;
	m_volume_target			= k_min_volume;
	m_volume_now			= k_min_volume;
	m_play_position_state	= k_playing_uninitalized_buffer;
	m_type					= 0;
}

// ---------------------------------------------------------
// sound details
// ---------------------------------------------------------
t_sound_player::~t_sound_player()
{
	if (m_direct_sound_ptr->is_shut_down())
		return;

	t_data_locker lock( get_direct_sound().get_data_lock() );

	if (m_notify_object != 0)
		m_notify_object->Release();

	if (m_buffer != 0)
		m_buffer->Release();
}

// ---------------------------------------------------------
// set all the required callback events on the sound buffer
// ---------------------------------------------------------
void t_sound_player::create_events( int type )
{
	//we've switched over to a polling mechanisim instead of an event base system, due 
	//to some incompatablity with the newer operations systems (WinMe, Win2k, WinXP)

	return;
}


// ---------------------------------------------------------
// sound details
// ---------------------------------------------------------
void t_sound_player::create_buffer()
{
	if (strcmp(m_sound->get_extension(),"mp3")==0)
		m_type = 0;

	t_data_locker lock( get_direct_sound().get_data_lock() );
	
	if (m_buffer != 0)
		return;
	
	m_buffer = NULL;

	m_play_position_state = k_playing_uninitalized_buffer;

	// set format of sound buffer
	WAVEFORMATEX				   format;

    memset(&format, 0, sizeof(format)); 

    format.wFormatTag		= WAVE_FORMAT_PCM; 
    format.nChannels		= m_sound->get_channels();
    format.nSamplesPerSec	= m_sound->get_samples_per_second();
    format.wBitsPerSample	= m_sound->get_bits_per_sample();
    format.nBlockAlign		= format.wBitsPerSample / 8 * format.nChannels;
    format.nAvgBytesPerSec	= format.nSamplesPerSec * format.nBlockAlign;

	m_final_position		= m_data_remaining % m_buffer_size;
	m_buffer				= get_direct_sound().create_buffer( &format, m_buffer_size, m_type );

	assert( m_buffer );
	create_events( m_type );
	
	if (m_streaming)
		fill_first_half();
	else
		fill_buffer( 0, m_buffer_size );
}

// ---------------------------------------------------------
// sound details
// ---------------------------------------------------------
void t_sound_player::close_buffer()
{
	if (m_direct_sound_ptr->is_shut_down())
		return;

	t_data_locker lock( get_direct_sound().get_data_lock() );

	if (m_notify_object != 0)
	{
		m_notify_object->Release();
		m_notify_object = 0;
	}

	if (m_buffer != 0)
	{
		m_buffer->Release();
		m_buffer = 0;
	}
	
	m_sync_list.clear();
}

// ------------------------------------------------------
// playing sound
// ------------------------------------------------------
int t_sound_player::get_balance() const
{
	long result;
	if (!m_playing) return 0;

	t_data_locker lock( get_direct_sound().get_data_lock() );

	m_buffer->GetPan( &result );
	return result;
}

int t_sound_player::get_type() const
{
	return m_type;
}

// ------------------------------------------------------
// playing sound
// ------------------------------------------------------
int t_sound_player::get_volume() const
{
	if (!m_playing) 
		return k_min_volume;

	t_data_locker lock( get_direct_sound().get_data_lock() );

#ifndef NDEBUG

	long ds_volume;
	m_buffer->GetVolume( &ds_volume );
	assert( m_volume_now == ds_volume );

#endif

	return m_volume_now;
}

// ------------------------------------------------------
// playing sound
// ------------------------------------------------------
void t_sound_player::set_balance( int arg )
{
	if (!m_playing) return;

	t_data_locker lock( get_direct_sound().get_data_lock() );

	m_buffer->SetPan( arg );
}

// ------------------------------------------------------
// playing sound
// ------------------------------------------------------
void t_sound_player::set_volume( int arg, bool interpolate_to_volume )
{
	if (!m_playing) return;

	t_data_locker lock( get_direct_sound().get_data_lock() );

	m_volume_target = arg;

	m_last_update_time = get_time();

	if( !interpolate_to_volume )
	{
		// We're not interpolating, so just set it now
		m_volume_now = arg;
		assert( arg >= k_min_volume && arg <= k_max_volume );
		m_buffer->SetVolume( arg );
	}
}

// ------------------------------------------------------
// playing sound
// ------------------------------------------------------
void t_sound_player::on_stop()
{
	t_data_locker lock( get_direct_sound().get_data_lock() );

	if (m_playing)
	{
		m_playing = false;
		get_direct_sound().remove( this );
	}
}

// ---------------------------------------------------------
// sound details
// ---------------------------------------------------------
void t_sound_player::fill_first_half()
{
	t_data_locker lock( get_direct_sound().get_data_lock() );

	if (m_final_position == m_second_half_start && m_final_pass)
	{
		stop();
		return;
	}
	
	if (m_final_pass)
	{
		stop();
		return;
	}


	fill_buffer( 0, m_second_half_start );
}

// ---------------------------------------------------------
// sound details
// ---------------------------------------------------------
void t_sound_player::fill_second_half()
{
	t_data_locker lock( get_direct_sound().get_data_lock() );

	if (m_final_position == 0 && m_final_pass)
	{
		stop();
		return;
	}

	if (m_final_pass)
	{
		stop();
		return;
	}
	
	
	fill_buffer( m_second_half_start, m_buffer_size );
}

// ---------------------------------------------------------
// sound details
// ---------------------------------------------------------
void t_sound_player::check_stop()
{
	t_data_locker lock( get_direct_sound().get_data_lock() );

	if (m_final_pass)
	{
		stop();
	}
}

// ------------------------------------------------------
// playing sound
// ------------------------------------------------------
void t_sound_player::play( int volume, bool looping, bool fade_in )
{
	t_data_locker lock( get_direct_sound().get_data_lock() );

	m_looping			= looping;
	m_volume_target		= volume;
	m_last_update_time	= get_time();

	// If the volume is "off", just stop the sound and return
	if (volume <= k_min_volume)
	{
		stop();
		return;
	}

	if (m_playing )
	{
		// We're already playing, so just see if we need an immediate volume change
		if (!fade_in)
		{
			// Just set it now since we don't want a fade
			m_volume_now = volume;
			assert( m_volume_now >= k_min_volume && m_volume_now <= k_max_volume );
			m_buffer->SetVolume( m_volume_now );
		}

	} else {

		// We're not already playing, so lets get started
		if (fade_in)
			m_volume_now = k_min_volume;
		else
			m_volume_now = volume;

		if (m_buffer == 0)
			create_buffer();
		else
			create_events( m_type );

		assert( m_buffer );
		assert( m_volume_now >= k_min_volume && m_volume_now <= k_max_volume );

		m_buffer->SetVolume( m_volume_now );

		DWORD flags = DSBPLAY_TERMINATEBY_PRIORITY;
		
		if (m_looping || m_streaming)
			flags |= DSBPLAY_LOOPING;
		
		m_playing = (m_buffer->Play( 0, m_priority, flags ) >= 0);
		
		if (m_playing)
			get_direct_sound().add( this );
	}
}

// ------------------------------------------------------
// playing sound
// ------------------------------------------------------
void t_sound_player::stop( bool close_on_stop )
{
	if (m_buffer == NULL) return;

	t_data_locker lock( get_direct_sound().get_data_lock() );
	m_close_on_stop = close_on_stop;

	if (!m_playing)
		return;

	m_buffer->Stop();
	m_playing = false;

	if (close_on_stop)
		close_buffer();
	get_direct_sound().remove( this );
}


// ---------------------------------------------------------
// sound details
// ---------------------------------------------------------
void t_sound_player::fill_buffer( int start, int stop )
{
	int				size = stop - start;
	void*			buffer_1;
	DWORD			buffer_1_size;
	void*			buffer_2;
	DWORD			buffer_2_size;
	t_data_locker lock( get_direct_sound().get_data_lock() );

	m_buffer->Lock( start, size, &buffer_1, &buffer_1_size, &buffer_2, &buffer_2_size, 0 );

	// buffer 2 will always be zero, because we don't attempt to lock
	// beyond the end of the buffer
	assert( buffer_2 == NULL );
	assert( size == buffer_1_size );

	if (size > m_data_remaining)
		size = m_data_remaining;

	m_data_remaining -= size;
	m_sound_data->read( buffer_1, size );

	if (m_looping && m_data_remaining == 0)
	{
		m_sound_data = m_sound->get_stream();
		m_data_remaining = m_sound->get_size();
	}

	if (size < buffer_1_size)
	{
		char* buffer	= reinterpret_cast<char*>(buffer_1) + size;
		int   fill_size = buffer_1_size - size;
		
		if (m_looping)
		{
			assert( m_data_remaining > fill_size );
			m_sound_data->read( buffer, fill_size );
			m_data_remaining -= fill_size;
		}
		else
		{
			memset( buffer, 0, fill_size );
		}
	}

	m_buffer->Unlock( buffer_1, buffer_1_size, buffer_2, buffer_2_size );
	
	if (size == 0 && !m_looping)
		m_final_pass = true;
}

// ------------------------------------------------------
// begin fading in
// ------------------------------------------------------
void t_sound_player::fade_out( bool close_on_stop )
{
	t_data_locker lock( get_direct_sound().get_data_lock() );

	m_close_on_stop = close_on_stop;
	m_volume_target = k_min_volume;
	m_last_update_time = get_time();
}

bool t_sound_player::is_fading_out() const
{
	return m_volume_target < m_volume_now || m_volume_target == k_min_volume;
}

// ------------------------------------------------------
// service sound
// ------------------------------------------------------
void t_sound_player::update()
{
	t_data_locker lock( get_direct_sound().get_data_lock() );

	// self reference, because it's possible that m_sync_list will delete this object.
	t_counted_ptr<t_sound_player>	ref = this;
	long							diff = m_volume_target - m_volume_now;

	if (diff != 0)
	{
		int const k_delay = 100;

		if (elapsed_time( m_last_update_time ) >= k_delay)
		{
			m_last_update_time += k_delay;

			long step = (-m_volume_now >> 1) + 20;
			
			if (diff > 0) {
				if (step > diff)
					step = diff;
			} else {
				step = -step;
				if (step < diff)
					step = diff;
			}

			m_volume_now += step;

			if (m_volume_now <= k_min_volume)
			{
				stop();
				return;
			}

			assert( m_volume_now >= k_min_volume && m_volume_now <= k_max_volume );

			m_buffer->SetVolume( m_volume_now );
		}
	}

	//////////////////////////////////////////////////////////////////////////
	//
	//////////////////////////////////////////////////////////////////////////
	
	if ( m_streaming )
	{
		DWORD play_cursor;
		DWORD write_cursor; 
		
		switch (m_play_position_state)
		{
		case k_filled_first_half:
			
			//if the sound buffer is near the 2nd half then load up the 2nd half.
			
			m_buffer->GetCurrentPosition(
				&play_cursor, 
				&write_cursor 
				);
			
			if (play_cursor >= 0 && play_cursor < m_second_half_start)
			{
				m_play_position_state = k_filled_second_half;
				fill_second_half();
			}
			
			break;
			
		case k_filled_second_half:	
			
			//if the sound buffer is near the end of the 2nd half then load up the 1st half.
			
			m_buffer->GetCurrentPosition(
				&play_cursor, 
				&write_cursor 
				);
			
			if (play_cursor >= 0 && play_cursor > m_second_half_start)
			{
				m_play_position_state = k_filled_first_half;
				fill_first_half();
			}
			
			break;
			
		case k_playing_uninitalized_buffer:
			
			m_play_position_state = k_filled_first_half;
			
			break;
			
		default:
			
			//do nothing
			
			break;
		}
	}
	
	//handle the stop event.

	DWORD	sound_status;

	if ( m_playing && m_buffer->GetStatus( &sound_status ) == DS_OK )
	{
		if ( (sound_status & DSBSTATUS_PLAYING ) == 0)
			on_stop();
	}

	//////////////////////////////////////////////////////////////////////////
	//
	//////////////////////////////////////////////////////////////////////////

	m_sync_list.on_idle();
}


// ------------------------------------------------------
// begin playing sound
// ------------------------------------------------------
t_playing_sound_ptr t_sound::play( int volume, bool looping, bool fade_in ) const
{
	if (get() == 0 || get_direct_sound().get_directsound_ptr() == 0)
		return 0;

	t_counted_ptr<t_sound_player> player = new t_sound_player( get() );

	player->play( volume, looping, fade_in );
	return player;
}

// ------------------------------------------------------
// convert percentage volume to decibels
// ------------------------------------------------------
double const k_log_2 = 0.693147180559945309417232121458177;

int get_millibel_volume( int linear_volume )
{
	if (linear_volume <= 0)
		return k_min_volume;
	if (linear_volume >= 100)
		return k_max_volume;
	return (log( linear_volume * 0.01 ) / k_log_2) * 1000;
}

int get_linear_volume( int millibel_volume )
{
	if (millibel_volume <= k_min_volume)
		return 0;
	if (millibel_volume >= k_max_volume)
		return 100;
	return exp( millibel_volume * k_log_2 / 1000.0 ) * 100.0;
}

// ------------------------------------------------------
// Sound System stop_all_sounds
// ------------------------------------------------------
void stop_all_sounds()
{
	get_direct_sound().stop_sounds();
}
